#!/usr/bin/env python
# Filename: rw.py
# Version: 0.4.42a
# Author: lichray
# Licence: GPL2.0

import os
import sys
import re
import pickle
import os.path as path
from random import sample

if sys.platform[:3] == 'win':
	from msvcrt import getch
	use_curses = None
else:
	try:
		import curses
		use_curses = True
		win = curses.initscr()
		curses.nonl()
		curses.noecho()
		win.scrollok(True)
		getch = win.getkey
	except ImportError:
		use_curses = False

def pr (*s):
	nl = True
	ls = s
	if s != ():
		if s[-1] == True:
			ls = s[:-1]
		if s[-1] == False:
			nl = False
			ls = s[:-1]
	src = reduce(lambda a, b: a+b, ls, "")
	if use_curses:
		if nl: win.addstr(src+'\n')
		else: win.addstr(src)
		win.refresh()
	else:
		if nl: print src
		else: print src,

def readline ():
	if use_curses:
		curses.echo()
		l = win.getstr()
		curses.noecho()
		return l
	else: return sys.stdin.readline()

def cleanup ():
	if use_curses:
		curses.endwin()

def locate (f, i, n = 1):
	while f:
		if n == i:
			pr(nl, f[0], nl)
			return f[1:]
		if f[0] == "\n":
			n = n + 1
		f = f[1:]
	quit("# end of file")

def shuffle (l):
	return sample(l, len(l))

def sort (l):
	l.sort()
	return l

def struct (f, st = []):
	if f == [] or f[0] == nl:
		return st
	return struct(f[1:], st+[f[0].strip()])

def conmat (ls, l, tmp = []):
	if len(tmp) == l:
		return [tmp]+conmat(ls, l)
	if len(ls) <= l and tmp == []:
		return [ls]
	return conmat(ls[1:], l, tmp+[ls[0]])

def mani (cs):
	ss = re.split('\s+', cs)
	return (' '.join(ss[:-1]), ss[-1])

def flat (lss):
	return reduce(list.__add__, lss)

def flatmap (f, lss):
	return flat(map(f, lss))

def select (f, lss):
	return flatmap(lambda t: filter(f, t), lss)

def prall (ls):
	map(lambda l: sys.stdout.write('   '+l+nl), ls)

def lsall (ls):
	def iter (n):
		if n == len(ls): return
		pr("%2d  %s" % ((n+1)%10, ls[n]))
		iter(n+1)
	iter(0)
	pr()

def inter (st, wn = 1, cn = 0):
	'''usage: <command>
 <command> - 
  h : help                    q : quit
  n : next word               p : prev word
  d : down to next section    u : up to prev section
  r : review this list        s : shuffle this list
  v : view next section       b : back prev section
  f : forgot this word        t : take a test
  <digit> : add forgot word   <Enter> : continue
  j<number><Enter> : jump to section <number>
  l<number><Enter> : load the list <number>
  e<alphas><Enter> : explain the word <alphas>
  g<alphas><Enter> : generate words with <alphas>'''

	def term (rt, lt = [], n = 0, cur = 'v'):
		def exam (st, log):
			'''usage: <command>
 <command> - 
  h : help                    q : quit
  y : yeah and pass           b : back to learn'''

			def inter (m):
				def enter ():
					pr(' ', st[m][1], nl, '-'*28)
					inter(m+1)
	
				def help ():
					pr(exam.func_doc, nl)
					inter(m)

				def yeah ():
					pr(' ', st[m][1], nl*2, '-'*28)
					del st[m]
					inter(m)

				def back ():
					pr("# end of test\n")
					global fg
					fg = []
					pickle.dump(st, log)
					log.close()
					term(rt, lt, n, cur)

				def direct ():
					cmdtable = {
						'S': yeah,
					}

					cmd = getch()
					if cmdtable.has_key(cmd):
						cmdtable[cmd]()
					else: err()

				def funkey ():
					cmdtable = {
						';': help,
					}

					cmd = getch()
					if cmdtable.has_key(cmd):
						cmdtable[cmd]()
					else: err()

				def err ():
					inter(m)

				def quit ():
					pickle.dump(st, log)
					cleanup()
					sys.exit(0)

				cmdtable = {
					'\r': enter,
					' ': enter,
					'h': help,
					'y': yeah,
					'b': back,
					'\e': back,
					'\b': back,
					'q': quit,
					'\xe0': direct,
					'\x00': funkey,
				}

				if len(st) > m:
					pr(nl, '', st[m][0], nl)
				else: back()

				cmd = getch()
				if cmdtable.has_key(cmd):
					cmdtable[cmd]()
				else: err()
			inter(0)

		def enter ():
			if cur == 'v': view()
			elif cur == 'n': next()
			elif cur == 'p': prev()

		def back ():
			if cur == 'v': before()
			else: prev()

		def help ():
			pr(inter.func_doc, nl)
			cont()

		def next ():
			if rt == []:
				pr()
				term(lt , rt, n, 'n')
			pr(' ', rt[0], nl)
			term(rt[1:] , lt+[rt[0]], n, 'n')

		def prev ():
			if lt == []:
				pr()
				term(lt , rt, n, 'p')
			pr(' ', lt[-1], nl)
			term([lt[-1]]+rt, lt[:-1], n, 'p')

		def down ():
			if n == len(st)-1:
				term(st[0], [], n, cur)
			term(st[n+1], [], n+1, cur)
	
		def up ():
			if n == 0:
				term(st[len(st)-1], [], len(st)-1, cur)
			term(st[n-1], [], n-1, cur)

		def home ():
			term(st[0], n = 0)

		def end ():
			term(st[-1], n = len(st)-1)

		def review ():
			inter(conmat(sort(flat(st)), loc))

		def shuff ():
			inter(conmat(shuffle(flat(st)), loc))

		def jump ():
			pr("\n# jump to section ", False)
			p = readline().strip()
			pr()
			if p.isdigit():
				num = (int(p)-1)%len(st)
				term(st[num], n = num)
			else: err()

		def load ():
			pr("\n# load the list ", False)
			l = readline().strip()
			if l.isdigit():
				start(int(l))
			else: err()

		def expl ():
			pr("\n# explain the word ", False)
			p = readline().strip()
			if p != "":
				ls = select(lambda s: s.startswith(p), st)
				if ls == []:
					pr("# no word found")
				else:
					pr()
					prall(ls[:loc])
					pr("\n# %d words found" % len(ls))
			pr()
			cont()

		def gener ():
			pr("\n# generate words with ", False)
			p = readline()[:-1]
			if p !="":
				ls = select(lambda s: re.match(p, s), st)
				if ls == []:
					pr("# no word matched")
				else:
					pr("\n # view all %d words\n" % len(ls))
					inter(conmat(ls, loc))
			pr()
			cont()

		def view ():
			lsall(st[cn%len(st)])
			inter(st, wn, cn+1)

		def before ():
			lsall(st[(cn-2)%len(st)])
			inter(st, wn, cn-1)

		def forgot ():
			if lt != []:
				fg.append(mani(lt[-1]))
			next()

		def test ():
			pr("\n# test started")
			cwf = path.join('.rw', sys.argv[1])+'.log'
			if not path.isdir('.rw'):
				os.mkdir('.rw')
			try:
				exam(shuffle(fg+ \
						pickle.load(open(cwf))), \
						open(cwf, 'w'))
			except SystemExit: quit()
			except:
				if fg == []:
					pr(" # no word for test\n")
					term(rt, lt, n, cur)
			exam(shuffle(fg), open(cwf, 'w'))

		def direct ():
			cmdtable = {
				'K': prev,
				'M': next,
				'H': back,
				'P': enter,
				'I': up,
				'Q': down,
				'G': home,
				'O': end,
				'R': forgot,
			}

			cmd = getch()
			if cmdtable.has_key(cmd):
				cmdtable[cmd]()
			else: err()

		def funkey ():
			cmdtable = {
				';': help,
				'>': view,
				'?': review,
				'B': shuff,
			}

			cmd = getch()
			if cmdtable.has_key(cmd):
				cmdtable[cmd]()
			else: err()

		def err ():
			term(rt, lt, n, cur)

		def cont ():
			term(rt, lt, n, cur)

		def label (lb):
			tp = mani(st[(cn-1)%len(st)][lb-1])
			fg.append(tp)
			pr("# `%s' was added" % tp[0])
			cont()

		cmdtable = {
			'\r': enter,
			' ': enter,
			'\b': back,
			'q': quit,
			'h': help,
			'n': next,
			'p': prev,
			'd': down,
			'u': up,
			'j': jump,
			'l': load,
			'e': expl,
			'?': expl,
			'g': gener,
			'/': gener,
			'r': review,
			's': shuff,
			'v': view,
			'b': before,
			'f': forgot,
			't': test,
			'\xe0': direct,
			'\x00': funkey,
		}

		cmd = getch()
		if cmdtable.has_key(cmd):
			cmdtable[cmd]()
		elif cmd.isdigit():
			label(int(cmd))
		else: err()
	term(st[(cn-1)%len(st)])

def quit (m = ""):
	if m:
		pr(m)
		sys.exit(1)
	cleanup()
	sys.exit(0)

def start (wl = 1, m = 'n'):
	'''usage: python rw.py <filename> [list] [section]
  The wordlist named <filename> must be separated by blank lines;
  program will start with the [list] or first part of the wordlist;
  the each section will contain [section] or 8 words. '''

	if m == 'n':
		inter(conmat(struct \
			(locate(file, wl)), loc), wl)
	elif m == 'r':
		inter(conmat(shuffle(struct \
			(locate(file, wl))), loc), wl)


if __name__ == '__main__':
	pr("Rewords 0.4.42a on ", sys.platform, " By lichray ")
	fg = []
	nl = '\n'
	loc = 8
	if use_curses == False:
		quit("Your system does not support curses. ")
	if len(sys.argv) > 3:
		tl = int(sys.argv[3])
		if 0 < tl < 11:
			loc = tl
	if len(sys.argv) > 1:
		file = open(sys.argv[1]).readlines()
	else: quit(start.func_doc)
	if len(sys.argv) > 2:
		start(int(sys.argv[2]))
	else: start()
